package student.web.internal;

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import org.apache.log4j.Logger;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.MouseEvent;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.EventInterceptor;
import org.zkoss.zkplus.databind.DataBinder;


// -------------------------------------------------------------------------
/**
 * Intended as an event interceptor listener for a desktop, this class will
 * automatically reload binding values after relevant page events on any of the
 * desktop's pages.
 * 
 * @author Stephen Edwards
 * @author Last changed by $Author: vtwoods@gmail.com $
 * @version $Revision: 426 $, $Date: 2011-02-09 23:08:05 -0500 (Wed, 09 Feb
 *          2011) $
 */
public class BindingUpdater implements EventInterceptor
{
    // ~ Constructor ...........................................................

    // ----------------------------------------------------------
    /**
     * Creates a new event interceptor for the given desktop.
     */
    public BindingUpdater( Desktop desktop )
    {
        this.desktop = desktop;
    }


    // ~ Methods ...............................................................

    // ----------------------------------------------------------
    public void afterProcessEvent( Event event )
    {

        log.debug( "afterProcessEvent(" + event + ")" );
        if ( processing.size() > 0 )
        {
            if ( processing.peek() == event )
            {
                processing.pop();
            }
            else if ( processing.contains( event ) )
            {
                while ( processing.size() > 0 )
                {
                    if ( processing.pop() == event )
                    {
                        break;
                    }
                }
            }
        }
        if ( triggeredEvents.remove( event ) )
        {
            log.debug( "    event was triggered by binding update" );
            updatingBindings = false;
            return;
        }
        Component target = event.getTarget();
        if ( target != null && target.getId().startsWith( "_errorPage" ) )
            return;
        if ( !interestingEvent( event )
            && !event.getName().equals( "onLoadOnSave" ) )
        {
            // ignore these
            log.debug( "    ignoring non-interaction event" );
            return;
        }

        log.debug( "    starting binder loading" );
        updatingBindings = true;
        for ( Object pg : desktop.getPages() )
        {
            Page page = (Page)pg;
            Object binder = page.getAttribute( "binder" );
            if ( binder != null )
            {
                log.debug( "    loading bindings!" );
                ( (DataBinder)binder ).loadAll();
            }
        }
        updatingBindings = false;
        log.debug( "    finished binder loading" );
    }


    // ----------------------------------------------------------
    private boolean interestingEvent( Event event )
    {
        return event instanceof MouseEvent || event instanceof SelectEvent
            || event instanceof CheckEvent
            || event.getName().equals( "onClick" )
            || event.getName().equals( "onTimer" );
    }


    // ----------------------------------------------------------
    public Event beforePostEvent( Event event )
    {
        log.debug( "beforePostEvent(" + event + ")" );
        if ( updatingBindings )
        {
            if ( event instanceof SelectEvent )
            {
                log.debug( "    intercepting screened select" );
                return null;
            }
            log.debug( "    event will be screened" );
            triggeredEvents.add( event );
        }
        return event;
    }


    // ----------------------------------------------------------
    public Event beforeProcessEvent( Event event )
    {
        log.debug( "beforeProcessEvent(" + event + ")" );
        processing.push( event );
        if ( triggeredEvents.contains( event ) )
        {
            log.debug( "    event is being screened" );
            updatingBindings = true;
        }
        return event;
    }


    // ----------------------------------------------------------
    public Event beforeSendEvent( Event event )
    {
        log.debug( "beforeSendEvent(" + event + ")" );
        if ( updatingBindings )
        {
            log.debug( "    event will be screened" );
            triggeredEvents.add( event );
        }
        return event;
    }

    // ~ Instance/static variables .............................................

    private Desktop desktop;

    private Set<Event> triggeredEvents = new HashSet<Event>();

    private Stack<Event> processing = new Stack<Event>();

    private boolean updatingBindings = false;

    private static final Logger log = Logger.getLogger( BindingUpdater.class );
}
